﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
using LuatChoi;
using Utility;
using System.Net;
using System.Threading;


namespace Server
{
    public partial class frmServer : Form
    {
        //public event EventHandler 
        private List<ClientComputer> _clients;
        private TcpListener _serverListener;
        private List<VanCo> _dsVanCo;
        private UdpClient _serverUDP;
        Random r = new Random();

        public frmServer()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
        }

        private void frmServer_Load(object sender, EventArgs e)
        {
            _clients = new List<ClientComputer>();
            _dsVanCo = new List<VanCo>();

            _serverListener = new TcpListener(IPAddress.Any, 2222);
            _serverListener.Start();
            rtxt.WriteLog("Máy chủ đã khởi động, đang chờ kết nối đến...", Color.DarkGreen);
            _serverListener.BeginAcceptTcpClient(AcceptCallback, null);

            _serverUDP = new UdpClient(2223);
            _serverUDP.BeginReceive(UDPReceiveCallback, null);

        }

        private void UDPReceiveCallback(IAsyncResult asyncResult)
        {
            var ipep = new IPEndPoint(IPAddress.Any, 0);
            var data = _serverUDP.EndReceive(asyncResult, ref ipep);
            if (Encoding.UTF8.GetString(data) == "Find IP")
            {
                var n = _clients.Count;

                _serverUDP.BeginSend(BitConverter.GetBytes(n), 4, ipep, UDPSendCallback, null);
                rtxt.WriteLine(ipep + " đang tìm kiếm máy chủ...", Color.BlueViolet);
            }
            _serverUDP.BeginReceive(UDPReceiveCallback, null);
        }

        private void UDPSendCallback(IAsyncResult ar)
        {
            _serverUDP.EndSend(ar);
        }

        private void AcceptCallback(IAsyncResult asyncResult)
        {
            TcpClient tcpClient = _serverListener.EndAcceptTcpClient(asyncResult);
            _serverListener.BeginAcceptTcpClient(AcceptCallback, null);
            var client = new ClientComputer
                {
                    Client = tcpClient,
                    UserName = ""
                };
            rtxt.WriteLog("Đã chấp nhận kết nối từ :" + client.RemoteEndPoint);
            client.ResetBuffer();
            client.Stream.BeginRead(client.Buffer, 0, client.Buffer.Length, ReadCallback, client);
        }

        private void ReadCallback(IAsyncResult ar)
        {
            bool continueConnect = true;
            var client = ar.AsyncState as ClientComputer;
            client.Stream.EndRead(ar);
            var msg = new ChessMessage(client.Buffer);

            var msgReply = CreateNewMessage(msg.Type);

            switch (msg.Type)
            {
                case ChessMessageType.Login:
                    bool trungten;
                    lock ("_clients")
                    {
                        trungten = _clients.Any(cl => cl.UserName == msg.SenderName);
                    }
                    if (trungten) // trùng tên
                    {
                        continueConnect = false;
                        msgReply.Type = ChessMessageType.Logout;
                        SendTo(msgReply, client);
                        rtxt.WriteLog(
                            client.RemoteEndPoint + " đã đăng ký nickname " + msg.SenderName + " nhưng bị trùng!",
                            Color.Red);
                    }
                    else
                    {
                        client.UserName = msg.SenderName;
                        client.Status = ClientStatus.Free;
                        lock ("_clients")
                        {
                            _clients.Add(client);
                        }
                        SendTo(msgReply, client);
                        rtxt.WriteLog(client.RemoteEndPoint + " đã đăng nhập với nickname " + msg.SenderName,
                                       Color.LightSeaGreen);

                        RequestList(client.UserName);
                    }
                    break;

                case ChessMessageType.Logout:
                    continueConnect = false;
                    rtxt.WriteLog(client.UserName + " đã thoát", Color.Goldenrod);
                    lock ("_clients")
                    {
                        _clients.Remove(client);
                    }
                    RequestList();
                    break;
                case ChessMessageType.List:
                    ClientList ls;
                    lock ("_clients")
                    {
                        ls = new ClientList(_clients);
                    }
                    msgReply.Body = Encoding.UTF8.GetString(ls.ToByteArray());
                    SendTo(msgReply, client);
                    break;

                case ChessMessageType.Invite:
                    XuLyLoiMoi(client, msg);
                    break;
                case ChessMessageType.Chat:
                    SendChat(msg);
                    break;

                case ChessMessageType.Game:
                    XuLyGame(client, msg);
                    break;

            }

            if (continueConnect)
            {
                client.ResetBuffer();
                client.Stream.BeginRead(client.Buffer, 0, client.Buffer.Length, ReadCallback, client);
            }
        }

        private void XuLyGame(ClientComputer client, ChessMessage msg)
        {
            QuanCo quan;
            VanCo vanMoi;
            var gmsg = new GameMessage(msg.Body);
            var vanCo = _dsVanCo.FirstOrDefault(v => v.DsNguoiChoi.Any(n => n.Client.UserName == client.UserName));
            if (vanCo != null)
            {
                ClientComputer target;
                switch (gmsg.Type)
                {
                    case GameMessageType.KhoiTao:
                        break;
                    case GameMessageType.BatDau:
                        vanCo[client.UserName].TrangThai = TrangThaiNguoiChoi.DaSanSang;
                        if (vanCo.DsNguoiChoi.All(n => n.TrangThai == TrangThaiNguoiChoi.DaSanSang))
                        {
                            BatDauCuocChoi(vanCo);
                        }
                        break;
                    case GameMessageType.DiChuyen:
                        target = TimDoiPhuong(client.UserName, vanCo).Client;
                        quan = DiChuyen(vanCo, gmsg.ToaDoBatDau, gmsg.ToaDoKetThuc);
                        SendTo(msg, target);
                        if (quan is Vua)
                        {
                            var sgmsg = CreateNewGameMessage(GameMessageType.Thang);
                            var smsg = CreateNewMessage(ChessMessageType.Game);
                            smsg.Body = sgmsg.ToString();
                            SendTo(smsg, client);

                            sgmsg.Type = GameMessageType.Thua;
                            sgmsg.Type = GameMessageType.Thua;
                            smsg.Body = sgmsg.ToString();
                            SendTo(smsg, target);

                           
                            vanMoi = new VanCo(vanCo[Phe.Trang].Client, vanCo[Phe.Den].Client);
                            _dsVanCo.Remove(vanCo);
                            _dsVanCo.Add(vanMoi);

                        }
                        break;
                    case GameMessageType.ThoatGiuaChung:
                        target = TimDoiPhuong(client.UserName, vanCo).Client;
                        vanCo[Phe.Trang].Client.Status = ClientStatus.Free;
                        vanCo[Phe.Den].Client.Status = ClientStatus.Free;
                        RequestList();
                        _dsVanCo.Remove(vanCo);
                        rtxt.WriteLog(client.UserName + " đã thoát ra giữa chừng trong khi chơi với " + target.UserName);
                        SendTo(msg, target);
                        break;
                    case GameMessageType.KetThuc:
                        break;
                    case GameMessageType.Thang:
                        break;
                    case GameMessageType.Thua:
                        break;
                    case GameMessageType.DuocDi:
                        break;
                    case GameMessageType.XinBoCuoc:
                        target = TimDoiPhuong(client.UserName, vanCo).Client;
                        vanCo[Phe.Trang].TrangThai=TrangThaiNguoiChoi.ChuanBi;
                        vanCo[Phe.Den].TrangThai=TrangThaiNguoiChoi.ChuanBi;
                        vanCo.BanChoi=new BanCo();
                        SendTo(msg, target);
                        break;
                    case GameMessageType.PhongTot:
                        //target = TimDoiPhuong(client.UserName, vanCo).Client;
                        //PhongTot(vanCo, gmsg.ToaDoBatDau, gmsg.ToaDoKetThuc, gmsg.Comment);
                        //SendTo(msg, target);
                        target = TimDoiPhuong(client.UserName, vanCo).Client;
                        quan = DiChuyen(vanCo, gmsg.ToaDoBatDau, gmsg.ToaDoKetThuc);
                        PhongTot(vanCo, gmsg.ToaDoKetThuc, gmsg.Comment);
                        SendTo(msg, target);
                        if (quan is Vua)
                        {
                            var sgmsg = CreateNewGameMessage(GameMessageType.Thang);
                            var smsg = CreateNewMessage(ChessMessageType.Game);
                            smsg.Body = sgmsg.ToString();
                            SendTo(smsg, client);

                            sgmsg.Type = GameMessageType.Thua;
                            sgmsg.Type = GameMessageType.Thua;
                            smsg.Body = sgmsg.ToString();
                            SendTo(smsg, target);
                        }
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }

        NguoiChoi TimDoiPhuong(string userName, VanCo van)
        {
            return van.DsNguoiChoi.First(n => n.Client.UserName != userName);
        }

        QuanCo DiChuyen(VanCo van, Vector a, Vector b)
        {
            var quanBiAn = van.BanChoi[b];
            van.BanChoi[b] = van.BanChoi[a];
            van.BanChoi[a] = null;
            van.BanChoi[b].TrangThai = TrangThaiQuan.BinhThuong;
            return quanBiAn;
        }

        private void PhongTot(VanCo van, Vector a, string quan)
        {
            QuanCo quanCo;
            if (typeof(Tuong).Name.Equals(quan))
                quanCo = new Tuong();
            else if (typeof(Ma).Name.Equals(quan))
                quanCo = new Ma();
            else if (typeof(Xe).Name.Equals(quan))
                quanCo = new Xe();
            else
                quanCo = new Hau();

            quanCo.Mau = van.BanChoi[a].Mau;
            quanCo.ToaDo = van.BanChoi[a].ToaDo;
            quanCo.TrangThai = van.BanChoi[a].TrangThai;
            van.BanChoi[a] = quanCo;
        }



        void XuLyLoiMoi(ClientComputer client, ChessMessage msg)
        {
            var ivm = new InviteMessage(msg.Body);
            switch (ivm.Type)
            {
                case InviteMessageType.Send:
                    rtxt.WriteLog(client.UserName + " đã mời " + ivm.Target, Color.CadetBlue);
                    break;
                case InviteMessageType.OK:
                    rtxt.WriteLog(client.UserName + " đã đồng ý lời mời của " + ivm.Target, Color.CadetBlue);
                    break;
                case InviteMessageType.Cancel:
                    rtxt.WriteLog(client.UserName + " đã từ chối lời mời của " + ivm.Target,
                                   Color.CadetBlue);
                    break;
            }
            ClientComputer target;
            lock ("_clients")
            {
                target = _clients.Single(c => c.UserName.Equals(ivm.Target));
            }
            if (ivm.Type != InviteMessageType.OK)
            {
                var inviteMsg = CreateNewMessage(ChessMessageType.Invite, client.UserName);
                //ivm.Target = msg.SenderName;
                inviteMsg.Body = ivm.ToString();
                SendTo(inviteMsg, target);
            }
            else
            {
                lock ("_dsVanCo")
                {
                    if (_dsVanCo.All(v => v.DsNguoiChoi.All(n => !n.Client.UserName.Equals(client.UserName) && !n.Client.UserName.Equals(target.UserName))))
                    {
                        var vanCo = new VanCo(target, client);
                        _dsVanCo.Add(vanCo);
                        KhoiTaoVanCo(vanCo);
                    }
                }
            }
        }

        //gửi lệnh khỏi tạo ván cờ đến 2 client
        void KhoiTaoVanCo(VanCo vanCo)
        {
            var gMsg = CreateNewGameMessage(GameMessageType.KhoiTao);
            var msg = CreateNewMessage(ChessMessageType.Game);

            gMsg.ToaDoBatDau = new Vector(7, 7);
            msg.SenderName = vanCo[Phe.Trang].Client.UserName;
            msg.Body = gMsg.ToString();
            SendTo(msg, vanCo[Phe.Den].Client);

            gMsg.ToaDoBatDau = new Vector(0, 0);
            msg.SenderName = vanCo[Phe.Den].Client.UserName;
            msg.Body = gMsg.ToString();
            SendTo(msg, vanCo[Phe.Trang].Client);
            rtxt.WriteLog("Đã khởi tạo ván cờ giữa " + vanCo[Phe.Trang].Client.UserName + " và " + vanCo[Phe.Den].Client.UserName, Color.Navy);

            vanCo[Phe.Trang].Client.Status = ClientStatus.InRoom;
            vanCo[Phe.Den].Client.Status = ClientStatus.InRoom;
            RequestList();
        }

        //gửi lệnh cho phép bắt đầu cuộc chơi đến tất cả người chơi trong 1 ván cờ
        private void BatDauCuocChoi(VanCo vanCo)
        {
            var gMsg = CreateNewGameMessage(GameMessageType.BatDau);
            var msg = CreateNewMessage(ChessMessageType.Game);

            msg.Body = gMsg.ToString();
            SendTo(msg, vanCo[Phe.Trang].Client);
            SendTo(msg, vanCo[Phe.Den].Client);
            rtxt.WriteLog("Đã bắt đầu ván chơi giữa " + vanCo[Phe.Trang].Client.UserName + " và " + vanCo[Phe.Den].Client.UserName, Color.SandyBrown);
        }


        void SendChat(ChessMessage msg)
        {
            try
            {
                var cmsg = new ChatMessage(msg.Body);
                ClientComputer target;
                lock ("_clients")
                {
                    target = _clients.Single(c => c.UserName.Equals(cmsg.SendTo));
                }
                SendTo(msg, target);
                rtxt.WriteLog(msg.SenderName + " -> " + cmsg.SendTo + " : " + cmsg.Message, Color.Navy);
            }
            catch
            {
                rtxt.WriteLog("Lỗi khi xử lý câu lẹnh chat", Color.Red);
            }

        }

        private void SendTo(ChessMessage msg, ClientComputer client)
        {
            byte[] buff = msg.ToByteArray();
            lock (client.UserName)
            {
                client.Stream.BeginWrite(buff, 0, buff.Length, WriteCallback, client);
            }
        }

        private void RequestList(string ignore = "")
        {
            ChessMessage bMsg = CreateNewMessage(ChessMessageType.List);
            bMsg.Body = Encoding.UTF8.GetString((new ClientList(_clients)).ToByteArray());
            BroadcastMessage(bMsg, ignore);
        }

        private ChessMessage CreateNewMessage(ChessMessageType type = ChessMessageType.Other, string senderName = "Server")
        {
            return new ChessMessage()
                {
                    Type = type,
                    SenderName = senderName
                };
        }

        private GameMessage CreateNewGameMessage(GameMessageType type, string target = "")
        {
            return new GameMessage()
            {
                Type = type,
                Target = target
            };
        }


        private void BroadcastMessage(ChessMessage msg, string ignoreName = "")
        {
            lock ("_clients")
            {
                foreach (ClientComputer client in _clients)
                {
                    if (!client.UserName.Equals(ignoreName))
                        SendTo(msg, client);
                }
            }
        }

        private void WriteCallback(IAsyncResult ar)
        {
            var client = ar.AsyncState as ClientComputer;
            client.Stream.EndWrite(ar);
        }
    }
}